﻿using System;
using System.Collections.Generic;
using System.Linq;
using Game2DFramework;
using Game2DFramework.Drawing;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MechGame.Utility
{
    class Menu : GameObject
    {
        private const float TransitionTime = 3.0f;
        private const int BorderPadding = 16;

        private readonly SpriteFont _menuFont;
        private readonly NinePatchSprite _ninePatch;
        private float _alphaDelta;
        private readonly List<MenuItem> _menuItems;
        private Rectangle _menuRectangle;
        private int _selectedIndex;

        public MenuState State { get; private set; }

        public Menu(Game2D game, MenuState startupState = MenuState.Hidden) : base(game)
        {
            _ninePatch = new NinePatchSprite(Game.Content.Load<Texture2D>("Textures/Panel"), BorderPadding);
            _menuFont = Game.Content.Load<SpriteFont>("Fonts/DialogFont");
            State = startupState;
            _alphaDelta = startupState == MenuState.Hidden ? 0.0f : 1.0f;
            _menuItems = new List<MenuItem>();
            _selectedIndex = 0;
        }

        public void AppendMenuItem(MenuItem menuItem)
        {
            _menuItems.Add(menuItem);
            Measure();
        }

        private void Measure()
        {
            var currentMaxWidth = (int)_menuItems.Select(m => _menuFont.MeasureString(m.Text).X).Max();
            var menuItemHeight = _menuFont.LineSpacing*3/2;

            _menuRectangle = new Rectangle
            {
                Width = currentMaxWidth + 4 * BorderPadding,
                Height = menuItemHeight * _menuItems.Count + 2 * BorderPadding
            };
            _menuRectangle.X = (Game.ScreenWidth - _menuRectangle.Width) / 2;
            _menuRectangle.Y = (Game.ScreenHeight - _menuRectangle.Height) / 2;

            var startY = _menuRectangle.Y + BorderPadding;
            var menuItemWidth = _menuRectangle.Width - 2*BorderPadding;
            foreach (var menuItem in _menuItems)
            {
                menuItem.TargetRectangle = new Rectangle(_menuRectangle.Left + BorderPadding, startY, menuItemWidth, menuItemHeight);
                startY += menuItemHeight;
            }
        }

        public void Show()
        {
            if (State != MenuState.Hidden) throw new InvalidOperationException("State must be hidden to show");
            State = MenuState.HideToShow;
        }

        public void Hide()
        {
            if (State != MenuState.Shown) throw new InvalidOperationException("State must be shown to hide");
            State = MenuState.ShowToHide;
        }

        public void Update(float elapsedTime)
        {
            switch(State)
            {
                case MenuState.HideToShow:
                    UpdateHideToShow(elapsedTime);
                    break;
                case MenuState.ShowToHide:
                    UpdateShowToHide(elapsedTime);
                    break;
                case MenuState.Shown:
                    UpdateShown();
                    break;
            }
        }

        private void UpdateShown()
        {
            if (Game.Keyboard.IsKeyDownOnce(Keys.Up))
            {
                if (--_selectedIndex < 0) _selectedIndex = _menuItems.Count - 1;
            }
            else if (Game.Keyboard.IsKeyDownOnce(Keys.Down))
            {
                if (++_selectedIndex == _menuItems.Count) _selectedIndex = 0;
            }

            if (Game.Mouse.MouseMoved())
            {
                var selectedMenuItem = _menuItems.FirstOrDefault(m => m.TargetRectangle.Contains(Mouse.GetState().X, Mouse.GetState().Y));
                if (selectedMenuItem != null)
                    _selectedIndex = _menuItems.IndexOf(selectedMenuItem);
            }

            if (Game.Keyboard.IsKeyDownOnce(Keys.Enter) || Game.Mouse.IsLeftButtonDownOnce()) _menuItems[_selectedIndex].InvokeAction();
        }

        private void UpdateShowToHide(float elapsedTime)
        {
            _alphaDelta -= elapsedTime * TransitionTime;
            if (_alphaDelta <= 0.0f)
            {
                _alphaDelta = 0.0f;
                State = MenuState.Hidden;
            }
        }

        private void UpdateHideToShow(float elapsedTime)
        {
            _alphaDelta += elapsedTime*TransitionTime;
            if (_alphaDelta >= 1.0f)
            {
                _alphaDelta = 1.0f;
                State = MenuState.Shown;
            }
        }

        public void Draw()
        {
            if (State == MenuState.Hidden) return;

            var alpha = MathHelper.SmoothStep(0, 1, _alphaDelta);
            var color = Color.White*alpha;
            _ninePatch.Draw(Game.SpriteBatch, _menuRectangle, color);

            for (var i = 0; i < _menuItems.Count; ++i)
            {
                var item = _menuItems[i];
                if (_selectedIndex == i)
                {
                    _ninePatch.Draw(Game.SpriteBatch, item.TargetRectangle, color);
                }
                item.Draw(Game.SpriteBatch, _menuFont, alpha);
            }
        }
    }
}
